package org.moyrax.compressor;

import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;

import org.mozilla.javascript.ErrorReporter;

import com.yahoo.platform.yui.compressor.CssCompressor;
import com.yahoo.platform.yui.compressor.JavaScriptCompressor;

/**
 * Compress a file and allows to specify several compression flags.
 *
 * @author Matias Mirabelli &lt;lumen.night@gmail.com&gt;
 * @since 0.1.0
 */
public class DefaultCompressor implements Compressor {
  /** Compress a CSS file. */
  public static final String TYPE_CSS = ".css";

  /** Compress a JavaScript source file. */
  public static final String TYPE_JS = ".js";

  /**
   * Error reporter to notify errors thrown by this compressor.
   */
  private final ErrorReporter errorReporter;

  /**
   * Some source control tools don't like files containing lines longer than,
   * say 8000 characters. The linebreak option is used in that case to split
   * long lines after a specific column. It can also be used to make the code
   * more readable, easier to debug (especially with the MS Script Debugger)
   * Specify 0 to get a line break after each semi-colon in JavaScript, and
   * after each rule in CSS.
   */
  public int linebreak = -1;

  /**
   * Sets or returns if the compressed output should be obfuscated. By default,
   * the output is not obfuscated.
   */
  public boolean munge;

  /**
   * Sets or returns if the compressor will log informational messages and
   * warnings. By default, no additional information will be logged.
   */
  public boolean verbose;

  /**
   * Sets or returns if the compressor will preserve unnecessary semicolons
   * (such as right before a '}') This option is useful when compressed code
   * has to be run through JSLint (which is the case of YUI for example).
   */
  public boolean preserveAllSemiColons;

  /**
   * Disable all the built-in micro optimizations.
   */
  public boolean disableOptimizations;

  /**
   * Construct a new DefaultCompressor with default options, and uses the
   * specified error reporter to notify errors thrown by this compressor.
   *
   * @param errorReporter  {@link ErrorReporter} used by this compressor.
   */
  public DefaultCompressor(final ErrorReporter errorReporter) {
    this.errorReporter = errorReporter;
  }

  /**
   * Construct a new DefaultCompressor with default options.
   */
  public DefaultCompressor() {
    this.errorReporter = new CompressorErrorReporter();
  }

  /**
   * Compress the specified file and writes the result to a new file.
   *
   * @param file        File to be compressed.
   * @param outputFile  File which will contain the compressor results.
   *
   * @throws CompressorException
   */
  public void compress(final File file, final File outputFile)
      throws CompressorException {

    byte[] result = this.compress(file);

    try {
      final FileWriter writer = new FileWriter(outputFile);

      writer.write(new String(result));
      writer.close();

    } catch (IOException ex) {
      throw new CompressorException("Error writing the output file.", ex);
    }
  }

  /**
   * Compress the specified file.
   *
   * @param file  File to be compressed.
   * @throws CompressorException
   */
  public byte[] compress(final File file) throws CompressorException {
    final String type = file.getName().endsWith(".css") ? TYPE_CSS : TYPE_JS;
    byte[] result = null;

    try {
      result = this.compress(new FileReader(file), type);
    } catch(FileNotFoundException ex) {
      throw new CompressorException("The input file doesn't exists.", ex);
    }

    return result;
  }

  /**
   * Compress the data provided by the specified reader.
   *
   * @param reader Reader which provides the data to be compressed.
   * @param type   Type of compression to be performed. The possible values are
   *               defined in the TYPE_CSS and TYPE_JS constants.
   *
   * @throws CompressorException
   */
  public byte[] compress(final Reader reader, final String type)
      throws CompressorException {

    try {
      final CharArrayWriter writer = new CharArrayWriter();

      if (type.equals(TYPE_CSS)) {
        final CssCompressor compressor = new CssCompressor(reader);

        /* Compress the stylesheet. Don't break lines. */
        compressor.compress(writer, -1);
      } else {
        final JavaScriptCompressor compressor = new JavaScriptCompressor(
            reader, errorReporter);

         compressor.compress(writer, linebreak, munge, verbose,
             preserveAllSemiColons, disableOptimizations);
      }

      return new String(writer.toCharArray()).getBytes();
    } catch (IOException ex) {
      throw new CompressorException("Error reading input source.", ex);
    }
  }
}
